package com.ruihu.admin.common.word;

import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.exception.RenderException;
import com.deepoove.poi.policy.RenderPolicy;
import com.deepoove.poi.render.compute.EnvModel;
import com.deepoove.poi.render.compute.RenderDataCompute;
import com.deepoove.poi.render.processor.DocumentProcessor;
import com.deepoove.poi.render.processor.EnvIterator;
import com.deepoove.poi.resolver.TemplateResolver;
import com.deepoove.poi.template.ElementTemplate;
import com.deepoove.poi.template.MetaTemplate;
import com.deepoove.poi.template.run.RunTemplate;
import com.deepoove.poi.util.ReflectionUtils;
import com.deepoove.poi.util.TableTools;
import org.apache.poi.xwpf.usermodel.*;
import org.apache.xmlbeans.XmlCursor;
import org.apache.xmlbeans.XmlObject;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.*;

import java.math.BigInteger;
import java.util.Iterator;
import java.util.List;

/**
 * Word表格列循環
 */
public class RHLoopRowColumnTableRenderPolicy implements RenderPolicy {
    private String prefix;
    private String suffix;
    private boolean onSameLine;

    private int columnMaxCountPerRow;

    public RHLoopRowColumnTableRenderPolicy() {
        this(false);
    }

    public RHLoopRowColumnTableRenderPolicy(boolean onSameLine) {
        this("[", "]", onSameLine, 3);
    }

    public RHLoopRowColumnTableRenderPolicy(String prefix, String suffix, int columnMaxCountPerRow) {
        this(prefix, suffix, false, columnMaxCountPerRow);
    }

    public RHLoopRowColumnTableRenderPolicy(String prefix, String suffix, boolean onSameLine, int columnMaxCountPerRow) {
        this.prefix = prefix;
        this.suffix = suffix;
        this.onSameLine = onSameLine;
        this.columnMaxCountPerRow = columnMaxCountPerRow;
    }

    @Override
    public void render(ElementTemplate eleTemplate, Object data, XWPFTemplate template) {
        RunTemplate runTemplate = (RunTemplate) eleTemplate;
        XWPFRun run = runTemplate.getRun();
        try {
            if (!TableTools.isInsideTable(run)) {
                throw new IllegalStateException(
                        "The template tag " + runTemplate.getSource() + " must be inside a table");
            }
            XWPFTableCell tagCell = (XWPFTableCell) ((XWPFParagraph) run.getParent()).getBody();
            XWPFTable table = tagCell.getTableRow().getTable();
            run.setText("", 0);

            if (null != data && data instanceof Iterable) {
                this.createTable(table, table.getRow(0), tagCell, (Iterable<?>) data);

                Iterator<?> iterator = ((Iterable<?>) data).iterator();

                TemplateResolver resolver = new TemplateResolver(template.getConfig().copy(prefix, suffix));

                int dataSize = this.getSize((Iterable<?>) data);
                int rowSize = this.getRowSize((Iterable<?>) data);
                int colSize = this.columnMaxCountPerRow < dataSize ? this.columnMaxCountPerRow : dataSize;

                int index = 0;
                int rowIndex = 0;
                int cellIndex = 0;
                boolean hasNext = iterator.hasNext();
                while (hasNext) {
                    Object root = iterator.next();
                    hasNext = iterator.hasNext();

                    XWPFTableCell cell = table.getRow(rowIndex).getCell(cellIndex++);
                    if (cellIndex == colSize) {
                        // 循环到最后一列后新增到下一行开始
                        rowIndex++;
                        cellIndex = 0;
                    }
                    RenderDataCompute dataCompute = template.getConfig()
                            .getRenderDataComputeFactory()
                            .newCompute(EnvModel.of(root, EnvIterator.makeEnv(index++, hasNext)));
                    List<MetaTemplate> templates = resolver.resolveBodyElements(cell.getBodyElements());
                    new DocumentProcessor(template, resolver, dataCompute).process(templates);
                }

                // 清除剩余的单元格EL表达式
                for (int i = rowIndex; i < rowSize; i++) {
                    for (int j = cellIndex; j < colSize; j++) {
                        List<XWPFParagraph> paragraphs = table.getRow(i).getCell(j).getParagraphs();
                        for (XWPFParagraph paragraph : paragraphs) {
                            int len = paragraph.getRuns().size();
                            for (int k = len - 1; k > -1; k--) {
                                paragraph.removeRun(k);
                            }
                        }
                    }
                }
            }

            afterloop(table, data);
        } catch (Exception e) {
            throw new RenderException("HackLoopTable for " + eleTemplate + "error: " + e.getMessage(), e);
        }
    }

    private void createTable(XWPFTable table, XWPFTableRow row, XWPFTableCell tagCell, Iterable<?> data) {
        int dataSize = this.getSize(data);
        int rowSize = this.getRowSize(data);
        int colSize = this.columnMaxCountPerRow < dataSize ? this.columnMaxCountPerRow : dataSize;


        int templateRowIndex = getTemplateRowIndex(tagCell);
        int templateColIndex = getTemplateColIndex(tagCell);

        int actualColIndex = getActualInsertPosition(tagCell.getTableRow(), templateColIndex);
        XWPFTableCell firstCell = tagCell.getTableRow().getCell(actualColIndex);
        int width = firstCell.getWidth();
        TableWidthType widthType = firstCell.getWidthType();
        if (TableWidthType.DXA != widthType || width == 0) {
            throw new IllegalArgumentException("template col must set width in centimeters.");
        }
        int insertColPosition = templateColIndex;
        int colWidth = processLoopColWidth(table, width, templateColIndex, data);
        // 创建模板列
        for (int i = 0; i < colSize; i++) {
            insertColPosition = templateColIndex++;
            int actualInsertPosition = getActualInsertPosition(row, insertColPosition);
            XWPFTableCell templateCell = row.getCell(actualInsertPosition);
            templateCell.setWidth(colWidth + "");
            XWPFTableCell nextCell = insertCell(row, actualInsertPosition);
            setTableCell(row, templateCell, actualInsertPosition);

            // double set row
            XmlCursor newCursor = templateCell.getCTTc().newCursor();
            newCursor.toPrevSibling();
            XmlObject object = newCursor.getObject();
            nextCell = new XWPFTableCell((CTTc) object, row, (IBody) nextCell.getPart());
            setTableCell(row, nextCell, actualInsertPosition);
        }
        // 删除多余的列
        removeCell(row, templateColIndex);

        // 创建模板行
        XWPFTableRow templateRow = table.getRow(templateRowIndex);
        int insertRowPosition = templateRowIndex;
        for (int i = insertRowPosition; i < rowSize; i++) {
            insertRowPosition = templateRowIndex++;
            XWPFTableRow nextRow = table.insertNewTableRow(insertRowPosition);
            setTableRow(table, templateRow, insertRowPosition);

            // double set row
            XmlCursor newCursor = templateRow.getCtRow().newCursor();
            newCursor.toPrevSibling();
            XmlObject object = newCursor.getObject();
            nextRow = new XWPFTableRow((CTRow) object, table);
            setTableRow(table, nextRow, insertRowPosition);
        }
        // 删除多余的行
        table.removeRow(templateRowIndex);
    }

    private int getTemplateRowIndex(XWPFTableCell tagCell) {
        XWPFTableRow tagRow = tagCell.getTableRow();
        return onSameLine ? getRowIndex(tagRow) : (getRowIndex(tagRow) + 1);
    }

    private int getRowIndex(XWPFTableRow row) {
        List<XWPFTableRow> rows = row.getTable().getRows();
        return rows.indexOf(row);
    }

    private void renderCell(Object data, XWPFTemplate template, TemplateResolver resolver,
                            XWPFTableCell nextCell, int index, boolean hasNext) {
        RenderDataCompute dataCompute = template.getConfig()
                .getRenderDataComputeFactory()
                .newCompute(EnvModel.of(data, EnvIterator.makeEnv(index++, hasNext)));
        List<MetaTemplate> templates = resolver.resolveBodyElements(nextCell.getBodyElements());
        new DocumentProcessor(template, resolver, dataCompute).process(templates);
    }

    private int getTemplateColIndex(XWPFTableCell tagCell) {
        return onSameLine ? getColIndex(tagCell) : (getColIndex(tagCell) + 1);
    }

    private void minusGridSpan(XWPFTableRow row, int templateColIndex) {
        XWPFTableCell actualCell = getActualCell(row, templateColIndex);
        CTTcPr tcPr = actualCell.getCTTc().getTcPr();
        CTDecimalNumber gridSpan = tcPr.getGridSpan();
        gridSpan.setVal(BigInteger.valueOf(gridSpan.getVal().longValue() - 1));
    }

    private void addColGridSpan(XWPFTableRow row, int insertPosition) {
        XWPFTableCell actualCell = getActualCell(row, insertPosition);
        CTTcPr tcPr = actualCell.getCTTc().getTcPr();
        CTDecimalNumber gridSpan = tcPr.getGridSpan();
        gridSpan.setVal(BigInteger.valueOf(gridSpan.getVal().longValue() + 1));
    }

    private int processLoopColWidth(XWPFTable table, int width, int templateColIndex, Object data) {
        CTTblGrid tblGrid = TableTools.getTblGrid(table);
        int dataSize = getSize((Iterable<?>) data);
        int colWidth = width / dataSize;
        // int colWidth = width;
        for (int j = 0; j < dataSize; j++) {
            CTTblGridCol newGridCol = tblGrid.insertNewGridCol(templateColIndex);
            newGridCol.setW(BigInteger.valueOf(colWidth));
        }
        tblGrid.removeGridCol(templateColIndex + dataSize);
        return colWidth;
    }

    private int processLoopColWidth(XWPFTable table, int width, int templateColIndex, int colSize) {
        CTTblGrid tblGrid = TableTools.getTblGrid(table);
        int colWidth = width / colSize;
        // int colWidth = width;
        for (int j = 0; j < colSize; j++) {
            CTTblGridCol newGridCol = tblGrid.insertNewGridCol(templateColIndex);
            newGridCol.setW(BigInteger.valueOf(colWidth));
        }
        tblGrid.removeGridCol(templateColIndex + colSize);
        return colWidth;
    }

    private int getSize(Iterable<?> data) {
        int size = 0;
        Iterator<?> iterator = data.iterator();
        while (iterator.hasNext()) {
            iterator.next();
            size++;
        }
        if (this.columnMaxCountPerRow < size)
            size = this.columnMaxCountPerRow;
        return size;
    }

    private int getRowSize(Iterable<?> data) {
        int size = 0;
        Iterator<?> iterator = data.iterator();
        while (iterator.hasNext()) {
            iterator.next();
            size++;
        }
        if (size == 0)
            return 0;

        return size / this.columnMaxCountPerRow + (size % this.columnMaxCountPerRow > 0 ? 1 : 0);
    }

    @SuppressWarnings("unchecked")
    private void removeCell(XWPFTableRow row, int actualInsertPosition) {
        List<XWPFTableCell> cells = (List<XWPFTableCell>) ReflectionUtils.getValue("tableCells", row);
        cells.remove(actualInsertPosition);
        row.getCtRow().removeTc(actualInsertPosition);

    }

    @SuppressWarnings("unchecked")
    private XWPFTableCell insertCell(XWPFTableRow tableRow, int actualInsertPosition) {
        CTRow row = tableRow.getCtRow();
        CTTc newTc = row.insertNewTc(actualInsertPosition);
        XWPFTableCell cell = new XWPFTableCell(newTc, tableRow, tableRow.getTable().getBody());

        List<XWPFTableCell> cells = (List<XWPFTableCell>) ReflectionUtils.getValue("tableCells", tableRow);
        cells.add(actualInsertPosition, cell);
        return cell;
    }

    protected void afterloop(XWPFTable table, Object data) {
    }

    @SuppressWarnings("unchecked")
    private void setTableCell(XWPFTableRow row, XWPFTableCell templateCell, int pos) {
        List<XWPFTableCell> rows = (List<XWPFTableCell>) ReflectionUtils.getValue("tableCells", row);
        rows.set(pos, templateCell);
        row.getCtRow().setTcArray(pos, templateCell.getCTTc());
    }

    private void setTableCell2(XWPFTableRow row, XWPFTableCell templateCell, int pos) {
        List<XWPFTableCell> rows = (List<XWPFTableCell>) ReflectionUtils.getValue("tableCells", row);
        rows.set(pos, templateCell);
        row.getCtRow().setTcArray(pos, (CTTc) templateCell.getCTTc().copy());
    }

    private void copyTableCell(XWPFTableRow row, XWPFTableCell templateCell, int pos) {
        XmlObject xmlObject = templateCell.getCTTc().copy();
        row.getCtRow().setTcArray(pos, (CTTc) xmlObject);
    }

    private void setTableRow(XWPFTable table, XWPFTableRow templateRow, int pos) {
        List<XWPFTableRow> rows = (List<XWPFTableRow>) ReflectionUtils.getValue("tableRows", table);
        rows.set(pos, templateRow);
        table.getCTTbl().setTrArray(pos, templateRow.getCtRow());
    }

    private int getColIndex(XWPFTableCell cell) {
        XWPFTableRow tableRow = cell.getTableRow();
        int orginalCol = 0;
        for (int i = 0; i < tableRow.getTableCells().size(); i++) {
            XWPFTableCell current = tableRow.getCell(i);
            int intValue = 1;
            CTTcPr tcPr = current.getCTTc().getTcPr();
            if (null != tcPr) {
                CTDecimalNumber gridSpan = tcPr.getGridSpan();
                if (null != gridSpan) intValue = gridSpan.getVal().intValue();
            }
            orginalCol += intValue;
            if (current.getCTTc() == cell.getCTTc()) {
                return orginalCol - intValue;
            }
        }
        return -1;
    }

    private int getActualInsertPosition(XWPFTableRow tableRow, int insertPosition) {
        int orginalCol = 0;
        for (int i = 0; i < tableRow.getTableCells().size(); i++) {
            XWPFTableCell current = tableRow.getCell(i);
            int intValue = 1;
            CTTcPr tcPr = current.getCTTc().getTcPr();
            if (null != tcPr) {
                CTDecimalNumber gridSpan = tcPr.getGridSpan();
                if (null != gridSpan) intValue = gridSpan.getVal().intValue();
            }
            orginalCol += intValue;
            if (orginalCol - intValue == insertPosition && intValue == 1) {
                return i;
            }
        }
        return -1;
    }

    private XWPFTableCell getActualCell(XWPFTableRow tableRow, int insertPosition) {
        int orginalCol = 0;
        for (int i = 0; i < tableRow.getTableCells().size(); i++) {
            XWPFTableCell current = tableRow.getCell(i);
            int intValue = 1;
            CTTcPr tcPr = current.getCTTc().getTcPr();
            if (null != tcPr) {
                CTDecimalNumber gridSpan = tcPr.getGridSpan();
                if (null != gridSpan) intValue = gridSpan.getVal().intValue();
            }
            orginalCol += intValue;
            if (orginalCol - 1 >= insertPosition) {
                return current;
            }
        }
        return null;
    }
}
